数据库总结:多数据库多租户多ORM
本节对整个数据库篇章进行系统性回顾与总结,涵盖 NestJS 对接多种 ORM 库的实践经验、多租户多数据库架构的设计思路,以及 Repository 抽象层的设计思想。
NestJS 的数据库对接架构
NestJS 上层应用
|
+-- HTTP Adapter (Express)
| |
| +-- 各类数据库驱动
|
+-- ORM 中间层
|
+-- TypeORM --> 关系型数据库 (MySQL, PostgreSQL, SQLite...)
+-- Prisma --> 关系型 + 非关系型 (MySQL, PostgreSQL, MongoDB...)
+-- Mongoose --> 非关系型数据库 (MongoDB)
text
NestJS 本身是一个上层应用框架,底层使用 Express 作为 HTTP Adapter。在数据库对接方面,NestJS 通过官方模块和社区模块提供了丰富的 ORM 集成能力。
三种 ORM 库的特点对比
| 特性 | TypeORM | Prisma | Mongoose |
|---|---|---|---|
| 数据库类型 | 关系型为主 | 关系型 + 部分非关系型 | 非关系型 (MongoDB) |
| 数据库版本支持 | 非常丰富 | 相对较少 | MongoDB 全版本 |
| 关系型数据库友好度 | 非常好 | 非常好 | 不适用 |
| 非关系型数据库支持 | 较弱 | 有但版本覆盖不足 | 专注且完善 |
| 多数据库连接 | 支持 | 支持 | 支持 |
| 异步配置 | forRootAsync | 自定义 forRootAsync | forRootAsync |
多租户数据库架构
三种"多"的含义
- 多 ORM 库 -- 同时使用 TypeORM + Prisma + Mongoose
- 多种数据库 -- 同时对接 MySQL、PostgreSQL、MongoDB
- 多库 -- 同类型数据库有多个实例(如 2 个 MySQL、3 个 PostgreSQL)
单库 vs 多库
| 维度 | 单库 | 多库 |
|---|---|---|
| 复杂度 | 低 | 高 |
| 维护成本 | 低 | 高 |
| 数据隔离 | 无(共享表空间) | 完全隔离 |
| 性能影响 | 无额外开销 | 连接管理开销 |
| 适用场景 | 小型项目启动 | 中大型项目、合规要求 |
配置来源
数据库配置通常有两个来源:
.env/ 配置文件 -- 最常见,适用于静态配置- 远端配置服务 -- 从配置中心或数据库动态读取租户配置
// 方式一:静态配置
TypeORMModule.forRoot({ url: process.env.DATABASE_URL });
// 方式二:动态配置(远端读取)
PrismaModule.forRootAsync({
useClass: PrismaConfigService, // 从远端获取配置
});
typescript
Repository 抽象层设计
在对接多种 ORM 库时,不同 ORM 的 API 差异很大:
- Prisma --
prismaClient.user.findMany() - TypeORM --
repository.find() - Mongoose --
userModel.find()
为了统一调用方式,需要引入抽象层:
用户调用层
|
v
UserRepository (统一接口)
|
+-- getRepository(tenantId) --> 根据租户选择具体实现
|
+-- UserPrismaRepository --> Prisma 的增删改查
+-- UserTypeORMRepository --> TypeORM 的增删改查
+-- UserMongooseRepository --> Mongoose 的增删改查
text
抽象类定义
// user-abstract.repository.ts
export abstract class UserAbstractRepository {
abstract find(): Promise<any[]>;
abstract create(userObj: any): Promise<any>;
abstract update(id: string, userObj: any): Promise<any>;
abstract delete(id: string): Promise<any>;
}
typescript
具体 Repository 实现
// Prisma 实现
export class UserPrismaRepository extends UserAbstractRepository {
constructor(@Inject('PRISMA_CLIENT') private client: PrismaClient) {
super();
}
async find() {
return this.client.user.findMany();
}
async create(userObj: any) {
return this.client.user.create({ data: userObj });
}
// ...
}
// TypeORM 实现
export class UserTypeORMRepository extends UserAbstractRepository {
constructor(@InjectRepository(User) private repo: Repository<User>) {
super();
}
async find() {
return this.repo.find();
}
async create(userObj: any) {
return this.repo.save(userObj);
}
// ...
}
// Mongoose 实现
export class UserMongooseRepository extends UserAbstractRepository {
constructor(@InjectModel('User') private userModel: Model<any>) {
super();
}
async find() {
return this.userModel.find().exec();
}
async create(userObj: any) {
return this.userModel.create(userObj);
}
// ...
}
typescript
统一的 UserRepository
export class UserRepository extends UserAbstractRepository {
constructor(
@Inject(REQUEST) private request: Request,
private prismaRepo: UserPrismaRepository,
private typeormRepo: UserTypeORMRepository,
private mongooseRepo: UserMongooseRepository,
) {
super();
}
private getRepository(): UserAbstractRepository {
const { tenantId } = this.request.headers;
// 根据 tenantId 路由到不同的 Repository 实现
switch (tenantId) {
case 'default': return this.prismaRepo;
case 'default1': return this.typeormRepo;
case 'default2': return this.mongooseRepo;
default: return this.prismaRepo;
}
}
async find() {
return this.getRepository().find();
}
// ... 代理其他方法
}
typescript
实际项目中的建议
- 大多数项目不需要三种 ORM -- 根据业务场景选择一种或两种即可
- 小项目从单库开始 -- 后续有需要再扩展到多库
- Repository 抽象层按需引入 -- 只有确实需要对接多种数据库时才创建
- 连接实例必须管理 -- 不论使用哪种 ORM,都需要处理连接复用和资源释放
- 模块化组织代码 -- 数据库相关代码封装在独立的 DatabaseModule 中,便于维护和替换
本节总结
- NestJS 通过 ORM 中间层可以对接几乎所有类型的数据库
- 不同 ORM 有各自的侧重点,选择时需考虑数据库类型和项目需求
- 多租户场景需要解决连接实例复用、资源释放、配置动态获取等问题
- Repository 抽象层(抽象类 + 接口)可以统一不同 ORM 的调用方式
- 实际项目应根据业务需求选择合适的架构复杂度,避免过度设计
↑